import chisel3._
import chisel3.util._
import chisel3.util.experimental._

class Clint extends Module {
  val io = IO(new Bundle {
    val in = Flipped(new CacheBusIO)
  })

  def maskExpand(x: UInt) = Cat(x.asBools.map(Fill(8, _)).reverse)
  val mtime = RegInit(UInt(64.W), 0.U)
  val mtimecmp = RegInit(UInt(64.W), 0.U)

  // Suppose the unit of mtime is us, core frequency is 100 MHz.
  // 1 us / 100 MHz = 100
  // val tick = RegInit(UInt(64.W), 1.U)
  // val counter = RegInit(UInt(64.W), 0.U)
  // counter := Mux(counter < tick, counter + 1.U, 0.U)
  // when (counter === tick) {
  //   mtime := mtime + 1.U
  // }
  mtime := mtime + 1.U

  val clint_addr = io.in.req.bits.addr(15, 0)
  val clint_rdata = WireInit(UInt(64.W), 0.U)
  val clint_ren = io.in.req.bits.ren & io.in.req.fire()
  val clint_wdata = io.in.req.bits.wdata
  val clint_wmask = maskExpand(io.in.req.bits.wmask)
  val clint_wen = io.in.req.bits.wen & io.in.req.fire()

  when (clint_addr(15,0) === "h4000".U && clint_ren) {
    clint_rdata :=  mtimecmp
  } .elsewhen (clint_addr(15,0) === "hbff8".U && clint_ren) {
    clint_rdata :=  mtime
  } otherwise {
    clint_rdata := 0.U
  }

  when (clint_addr(15,0) === "h4000".U && clint_wen) {
    mtimecmp :=  clint_wdata & clint_wmask
  } .elsewhen (clint_addr(15,0) === "hbff8".U && clint_wen) {
    mtime :=  clint_wdata & clint_wmask
  } 

  val reg_rdata = RegInit(UInt(64.W), 0.U)
  val s_idle :: s_resp_r :: s_resp_w :: Nil = Enum(3)
  val state = RegInit(s_idle)

  switch (state) {
    is (s_idle) {
      when (clint_ren) {
        reg_rdata := clint_rdata
        state := s_resp_r
      } .elsewhen (clint_wen) {
        state := s_resp_w
      }
    }
    is (s_resp_r) {
      when (io.in.resp.fire()) {
        state := s_idle
      }
    }
    is (s_resp_w) {
      when (io.in.resp.fire()) {
        state := s_idle
      }
    }
  }

  io.in.req.ready := (state === s_idle)
  io.in.resp.valid := (state =/= s_idle)
//  io.in.resp.bits.id := 2.U(AxiParameters.AxiIdWidth.W)
  io.in.resp.bits.rdata := reg_rdata
//  io.in.resp.bits.wresp := (state === s_resp_w)
//  io.in.resp.bits.rlast := (state === s_resp_r)

  val mtip = RegInit(0.U)
  mtip := (mtime >= mtimecmp).asUInt()
  BoringUtils.addSource(mtip, "mtip")
}